package com.cskayan.javase.object._6clone;

import javax.sound.midi.Soundbank;

/**
 * clone方法
 * 1.方法的声明：
 *       protected native Object clone() throws CloneNotSupportedException;
 *       a.首先注意访问权限修饰符是protected
 *          这意味着必须在子类中创建子类自身对象才能够调用clone方法
 *          但是这相当于自己克隆自己,一般没有太大的意义
 *          所以建议如果一个类需要做克隆操作,优先重写方法的访问权限修饰符
 *       b.这是一个本地方法,不需要方法体
 *       c.方法的返回值类型是Object
 *              这意味着子类重写该方法时,可以修改返回值类型为任意一个类.
 *       d.方法不需要参数.
 *       e.throws CloneNotSupportedException 方法抛出异常的声明,先不管.
 *
 * 2.方法的作用:
 *      这个方法的名字，其实已经很明显的告诉了你它的作用：
 *      克隆，生物学意义上的克隆，是用生物的体细胞，经过无性繁殖，得到相同基因型后代的过程。
 *      Java当中的克隆方法clone()有类似的作用，当你在程序中有这种需求，即希望：
 *          1. 得到一个和原先对象，完全独立的新对象。
 *          2. 成员仍和原先对象一致。
 *      有这种做法时，普通的做法就是再new一个一模一样的，但学习clone方法后，你就可以用该方法来做这个事情了。
 *      所以Object类当中的clone方法默认实现，就是得到一个独立的，和原先对象成员一致的新对象。
 *
 * 3.clone方法如何使用呢?
 *      a.如果不重写方法的访问权限修饰符,那么只能在自身类中自己克隆自己,意义不是很大
 *          所以首要的第一步是在子类中重写方法的访问权限修饰符
 *
 *      b.(选做)
 *      一般情况下,我们不需要重写Object类的clone方法实现,就直接使用就可以了.
 *      而Object类当中的clone方法默认实现,是肯定不会改变对象的类型的,得到的克隆对象一定仍然是原先的类型
 *      所以建议重写子类的返回值类型,改为自身对象类型
 *      避免一些多余的强转的工作
 *
 *      c.(重要)
 *      如果直接对一个类的对象做克隆操作,就会抛出CloneNotSupportedException,不支持克隆异常.
 *      这是因为,在java中如果想对某个类的对象做克隆操作,这个类就必须实现接口java.lang.Cloneable
 *      所以在对类的对象做克隆操作之前,这个类必须实现这个接口,不然就会抛出异常.
 *
 *      以上,完成了对某个类对象的克隆.
 *
 *
 * 注意事项(细节问题):
 *      1.clone方法创建对象和new对象
 *          new对象需要调用构造器赋值
 *          new几个对象,就要调用几个构造器
 *          但是clone方法创建对象依赖于本地方法实现
 *          和构造器没有任何关系
 *
 *          clone方法创建对象和new对象之间是完全平行的关系,创建对象互相是不影响的
 *
 *      2.接口Cloneable
 *      它是一个没有任何内容的空接口
 *      所以实现它不需要重写抽象方法
 *      那么:
 *          这样一个空接口和克隆操作有什么关系呢?
 *          空接口又有什么作用呢?
 *          空接口可以配合instanceof关键字来判断一个对象是否实现空接口
 *          并且这个接口起着标记的作用,只要一个类实现空接口,被空接口标记,就可以完成一些操作,反之则不行,会抛出异常
 *          这种接口称之为"标记接口"
 *
 *
 *     3.clone方法的重写.
 *     上面我们已经说过了，在进行克隆操作时，正常情况下，我们使用Object类当中的默实现就足够了，不需要重写实现。但假如你真的有需求，
 *     对于某个对象的引用x，JDK文档中也规定了一些重写的原则：
 * a. x.clone() != x 为 true
 *       克隆必须是一个新的独立的对象
 *
 * b. x.clone().getClass() == x.getClass() 一般也为true
 *      克隆最好不要改变对象的数据类型，除非你真的有需要。
 *
 * c. x.clone().equals(x) 一般情况下也为true
 *      克隆后的两个对象调用equals方法，应该返回true。前提是，必须按照成员变量的取值重写equals方法。
 *
 *      4.深度克隆的问题
 *      Object类当中clone方法的默认实现是浅克隆的:
 *          a.如果成员变量是基本数据类型,直接拷贝一份取值在新对象的中创建,这样拷贝后的对象和原先的对象,在基本数据类型成员变量上是不会互相影响的
 *          b.如果成员变量是引用数据类型的,那么就会把存储的引用拷贝一份在新对象中创建,这样拷贝后的引用和原先的引用仍然指向同一个对象,这样它们之间修改取值就会互相影响.这样就不符合克隆得到完全独立对象的定义
 *
 *     以上,浅克隆就不能达成需求了,就需要对类进行深度克隆.
 *     深度克隆指的是：对引用数据类型的成员变量引用所指向的对象，再克隆一次，然后让拷贝成员变量引用指向拷贝对象
 *          这样，深度克隆得到的对象，就会和原先的对象完全独立，是一个全新的对象。
 *
 *     深度克隆怎么做呢？
 *          1.深度克隆是在浅克隆的基础上完成的,所以深度克隆仍然要依赖于Object类当中的clone默认实现.
 *          2.将对象中引用所指向的对象,克隆一份
 *          3.将克隆后的对象的拷贝引用指向拷贝的对象
 *          以上,完成深度克隆
 *
 *
 *
 *
 *
 *
 *
 * @since 11:01
 * @author wuguidong@cskaoyan.onaliyun.com
 */
public class Demo {
    public static void main(String[] args) throws CloneNotSupportedException {
        // Student s = new Student(18, 100);
        // 'clone()' has protected access in 'java.lang.Object'
        // s.clone();

        // Unhandled exception: java.lang.CloneNotSupportedException
        /*Student cloneStu = s.clone();

        System.out.println(s == cloneStu);
        System.out.println(s.age == cloneStu.age);
        System.out.println(s.score == cloneStu.score);
        System.out.println(s.equals(cloneStu));*/

        Student s = new Student(18, 100, new Star(10000));
        Student cloneStu = s.clone();
        System.out.println(s == cloneStu);
        s.age = 28;
        System.out.println(cloneStu.age);
        s.star.salary = 20000;
        System.out.println(cloneStu.star.salary);

        System.out.println(s.star);
        System.out.println(cloneStu.star);

    }
}

// interface MyCloneable extends Cloneable{
// }
class Student implements Cloneable {
    int age;
    double score;

    Star star;

    public Student() {
    }

    public Student(int age, double score) {
        System.out.println("构造器执行了!");
        this.age = age;
        this.score = score;
    }

    public Student(int age, double score, Star star) {
        this.age = age;
        this.score = score;
        this.star = star;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Student student = (Student) o;

        if (age != student.age) return false;
        return Double.compare(student.score, score) == 0;
    }

    @Override
    public int hashCode() {
        int result;
        long temp;
        result = age;
        temp = Double.doubleToLongBits(score);
        result = 31 * result + (int) (temp ^ (temp >>> 32));
        return result;
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", score=" + score +
                '}';
    }

    /*
                    1.这里访问权限修饰符虽然保持一致,但仍然重写了访问权限,
                    因为现在这个成员已经换包,不再是java.lang包了
                    2.可以考虑重写方法的返回值类型，因为Object类的默认clone实现不会改变对象的数据类型
                    3.该类必须实现空接口java.lang.Cloneable
           如果想要做深度克隆,也需要重写这个方法.

     */
    @Override
    protected Student clone() throws CloneNotSupportedException {
        // 1.深度克隆是在浅克隆的基础上完成的.
        Student cloneStu = (Student) super.clone();
        // 2.将引用star指向的Star对象,克隆一份
        Star cloneStar = star.clone();
        // 3.将拷贝对象中的拷贝引用star指向克隆的Star对象
        cloneStu.star = cloneStar;
        return cloneStu;
    }
}

class Star implements Cloneable{
    double salary;

    public Star(double salary) {
        this.salary = salary;
    }

    public Star() {
    }

    @Override
    protected Star clone() throws CloneNotSupportedException {
        return ((Star) super.clone());
    }
}

